﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using xuni;

namespace xab
{


    public class BuildPreview : ScriptableObject
    {

        BuildRules _config;

        public List<RulePreview> luaRules = new List<RulePreview>();

        public List<RulePreview> topDirectoryRules = new List<RulePreview>();

        public List<RulePreview> explicitRules = new List<RulePreview>();

        public List<RulePreview> fileNameRules = new List<RulePreview>();

        public List<BundleInfo> allBundles = new List<BundleInfo>();

        public List<AssetInfo> redundantAssets = new List<AssetInfo>();

        /// <summary>
        /// bundle名 => BundleInfo
        /// </summary>
        private readonly Dictionary<string, BundleInfo> _bundleInfoCache = new Dictionary<string, BundleInfo>();

        /// <summary>
        /// 某个asset所在的Bundle, asset => BundleInfo
        /// </summary>
        private readonly Dictionary<string, BundleInfo> _asset2BundleInfo = new Dictionary<string, BundleInfo>();

        private readonly Dictionary<string, AssetInfo> _assetInfoCache = new Dictionary<string, AssetInfo>();

        /// <summary>
        /// 某个包场景文件和非场景文件混放就会被记录到这边, AbName => FilesInAb
        /// </summary>
        private readonly Dictionary<string, string[]> _conflicted = new Dictionary<string, string[]>();

        private StringBuilder _tempSb = new StringBuilder();

        List<string> _tempStrList = new List<string>();

        public void SetupDefault(BuildRules config)
        {
            _config = config;
        }

        public void GetAbBuilds(string specifyBundles, List<AssetBundleBuild> allAbBuilds, List<AssetBundleBuild> lz4AbBuilds, List<AssetBundleBuild> uncompressAbBuilds)
        {
            if (_bundleInfoCache.Count <= 0)
            {
                for (var i = 0; i < allBundles.Count; ++i)
                {
                    var bundleInfo = allBundles[i];
                    //Debug.Log($"GetAbBuilds: {bundleInfo.bundleName.name}");
                    _bundleInfoCache[bundleInfo.bundleName.name] = bundleInfo;
                }
            }

            var toBuildBundles = new List<BundleInfo>();
            var arr = specifyBundles.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
            for (var i = 0; i < arr.Length; ++i)
            {
                var specifyBundleName = arr[i];
                if (!specifyBundleName.EndsWith(Utils.Bundle_Ext, StringComparison.OrdinalIgnoreCase))
                {
                    _tempSb.Append(specifyBundleName).Append(Utils.Bundle_Ext);
                    specifyBundleName = _tempSb.ToString();
                    _tempSb.Clear();
                }

                _bundleInfoCache.TryGetValue(specifyBundleName, out var bundleInfo);
                if (null == bundleInfo)
                {
                    Debug.LogError($"GetAbBuilds: 指定的Bundle未找到: {specifyBundleName}");
                    continue;
                }

                Debug.Log($"GetAbBuilds: specify bundle: {specifyBundleName}, {bundleInfo.bundleName.name}, {bundleInfo.bundleName.displayName}");
                toBuildBundles.Add(bundleInfo);

                foreach (var assetInfo in bundleInfo.GetAssetInfoHasBundle())
                {
                    if (null != assetInfo.ownBundle
                        && !string.IsNullOrEmpty(assetInfo.ownBundle.name)
                        )
                    {
                        var bundleInfo2 = _bundleInfoCache[assetInfo.ownBundle.name];
                        if (null == bundleInfo2)
                            continue;

                        if (!toBuildBundles.Contains(bundleInfo2))
                        {
                            Debug.Log($"GetAbBuilds: bundle of deps asset: {assetInfo.assetPath}, {assetInfo.ownBundle.name}");
                            toBuildBundles.Add(bundleInfo2);
                        }
                    }
                }
            }

            GetAbBuilds(false, toBuildBundles, allAbBuilds, lz4AbBuilds, uncompressAbBuilds);
        }

        /// <summary>
        /// 根据ruleBundles生成资源构建所需的AssetBundleBuild数组
        /// </summary>
        /// <returns></returns>
        public void GetAbBuilds(List<AssetBundleBuild> allAbBuilds, List<AssetBundleBuild> lz4AbBuilds, List<AssetBundleBuild> uncompressAbBuilds)
        {
            GetAbBuilds(true, allBundles, allAbBuilds, lz4AbBuilds, uncompressAbBuilds);
        }

        void GetAbBuilds(bool isFull, List<BundleInfo> toBuildBundles, List<AssetBundleBuild> allAbBuilds, List<AssetBundleBuild> lz4AbBuilds, List<AssetBundleBuild> uncompressAbBuilds)
        {
            List<string> tempAssetList = new List<string>();

            for (int i = 0, max = toBuildBundles.Count; i < max; ++i)
            {
                var bundle = toBuildBundles[i];
                if (_config.displayProgressDialog)
                {
                    var title = string.Format("生成构建信息: {0}/{1}", i, max);
                    if (EditorUtility.DisplayCancelableProgressBar(title, "", i / (float)max))
                        break;
                }

                Debug.Log($"===== BuildPreview.GetAbBuilds: bundle: display:{bundle.bundleName.displayName}, name: {bundle.bundleName.name}");
                /*
                if (bundle.bundleName.name.StartsWith("lua/lua")) //lua的做特殊处理
                {
                    //#非完整构建时(指定ab包构建), lua可以不加进去(不让单独构建lua);
                    //#完整构建必须加进去, 不然MainAsset的manifest会索引不到lua的Bundle包
                    if (!isFull)
                    {
                        Debug.Log("GetAbBuilds: 非完整构建, lua文件不参与构建");
                    }
                    else
                    {
                        foreach (var assetInfo in bundle.GetAssetInfoHasBundle())
                        {
                            if (assetInfo.ownBundle.name == bundle.bundleName.name)
                            {
                                //# a.lua 实际被copy到了: Assets/_LuaTemp/a.lua.bytes
                                //# Common/a.lua 实际被copy到了: Assets/_LuaTemp/Common/a.lua.bytes

                                _tempSb.Append("Assets/_LuaTemp/");
                                _tempSb.Append(assetInfo.assetPath).Append(".bytes");
                                var assetPath = _tempSb.BuildString();
                                //Debug.Log($"GetAbBuilds: lua file: {assetInfo.asset} => {assetPath}");

                                tempAssetList.Add(assetPath);
                            }
                        }
                    }
                }
                else
                */

                {
                    foreach (var assetInfo in bundle.GetAssetInfoHasBundle())
                    {
                        if (assetInfo.ownBundle.name == bundle.bundleName.name)
                        {
                            if (true) Debug.Log($"GetAbBuilds: asset file: {assetInfo.assetPath}");
                            tempAssetList.Add(assetInfo.assetPath);
                        }
                    }
                }

                Debug.Log($"==========");

                var abBuild = new AssetBundleBuild
                {
                    assetNames = tempAssetList.ToArray(),
                    assetBundleName = bundle.bundleName.name,
                };
                allAbBuilds.Add(abBuild);

                switch (bundle.compressType)
                {
                case CompressType.None:
                    if (null != uncompressAbBuilds)
                        uncompressAbBuilds.Add(abBuild);
                    break;
                case CompressType.LZ4:
                    if (null != lz4AbBuilds)
                        lz4AbBuilds.Add(abBuild);
                    break;
                }

                tempAssetList.Clear();
            }
        }


        //#################### API End

        private static bool IsScene(string asset)
        {
            return asset.EndsWith(".unity");
        }

        /// <summary>
        /// 规则匹配到的资源为直接引用资源
        /// </summary>
        BundleInfo RuleMatchAsset(string assetPath, string ruleBundleName, string customDisplayName, CompressType type, bool obfuscation)
        {
            if (_config.allShaderIn1Bundle && ".shader" == Path.GetExtension(assetPath).ToLower())
            {
                var bundleInfo = ShaderFind(assetPath);
                //rulePreview.AddBundleInfo(bundleInfo);
                return bundleInfo;
            }
            else
            {
                if (ruleBundleName.IndexOf('\\') >= 0)
                    Debug.LogError($"ruleBundleName find \\: {ruleBundleName}");

                ruleBundleName = ruleBundleName.Replace('\\', '/');

                var displayName = ruleBundleName;
                if (!string.IsNullOrEmpty(customDisplayName))
                    displayName = customDisplayName;

                var bundleName = "";
                if (false && obfuscation)
                {
                    //_tempSb.Append(Utility.GetMd5Hash(ruleBundleName)).Append(Utils.Extension);
                    //bundleName = _tempSb.ToString();
                }
                else
                {
                    _tempSb.Append(ruleBundleName.ToLower()).Append(Utils.Bundle_Ext);
                    bundleName = _tempSb.ToString();
                }
                _tempSb.Clear();

                if (GetBundleInfo(bundleName, out var bundleInfo))
                {
                    bundleInfo.bundleName.displayName = displayName;
                    bundleInfo.compressType = type;
                }
                else
                {
                    if (bundleInfo.compressType != type)
                    {
                        Debug.LogError($"bundle有不同的压缩类型: {type}, 原因: 多条规则产生了相同的Bundle");
                    }
                    if (bundleInfo.bundleName.displayName != displayName)
                    {
                        Debug.LogError($"bundle有不同的displayName: {displayName}, 原因: 多条规则产生了相同的Bundle");
                    }
                }

                var assetInfo = GetAssetInfo(assetPath);
                bundleInfo.AddDirectIncludeAsset(assetInfo);

                _asset2BundleInfo[assetPath] = bundleInfo;

                //rulePreview.AddBundleInfo(bundleInfo);
                return bundleInfo;
            }
        }

        //BundleInfo AddBundleAsset(string bundleName, string displayName, string assetPath)
        //{
        //    var bundleInfo = GetBundleInfo(bundleName, displayName);

        //    bundleInfo.AddAsset(assetPath);
        //    _asset2BundleInfo[assetPath] = bundleInfo;
        //    return bundleInfo;
        //}

        //BundleInfo AddBundleDepAsset(string bundleName, string displayName, string assetPath)
        //{
        //    var bundleInfo = GetBundleInfo(bundleName, displayName);

        //    bundleInfo.AddDepAsset(assetPath);
        //    _asset2BundleInfo[assetPath] = bundleInfo;
        //    return bundleInfo;
        //}

        bool GetBundleInfo(string bundleName, out BundleInfo ret)
        {
            var isInit = false == _bundleInfoCache.TryGetValue(bundleName, out var bundleInfo);
            if (isInit)
            {
                bundleInfo = new BundleInfo(bundleName);
                _bundleInfoCache.Add(bundleName, bundleInfo);
            }
            ret = bundleInfo;

            return isInit;
        }

        AssetInfo GetAssetInfo(string assetPath)
        {
            if (!_assetInfoCache.TryGetValue(assetPath, out var assetInfo))
            {
                assetInfo = new AssetInfo(assetPath);
                _assetInfoCache.Add(assetPath, assetInfo);
            }

            return assetInfo;
        }


        /// <summary>
        /// 点击"执行"时调用, 刷新BuildRules类资源的RuleAssets和RuleBundles字段
        /// </summary>
        public void Apply()
        {
            Clear();
            CollectAssets();
            AnalysisAssets();
            OptimizeAssets();
            Save();

            Debug.Log("Finish !");
        }


        private void Clear()
        {
            luaRules.Clear();
            topDirectoryRules.Clear();
            explicitRules.Clear();
            fileNameRules.Clear();

            allBundles.Clear();
            redundantAssets.Clear();

            _bundleInfoCache.Clear();
            _asset2BundleInfo.Clear();
            _assetInfoCache.Clear();
            _conflicted.Clear();

            _tempSb.Clear();
            _tempStrList.Clear();
        }


        private void CollectAssets()
        {
            //========== lua rules
            if (_config.luaRulesEnable)
            {
                var config_luaRules = _config.luaRules;

                for (int i = 0, max = config_luaRules.Count; i < max; i++)
                {
                    var rule = config_luaRules[i];

                    if (_config.displayProgressDialog)
                    {
                        var title = string.Format("收集Lua资源: {0}/{1}", i, max);
                        if (EditorUtility.DisplayCancelableProgressBar(title, "", i / (float)max))
                            break;
                    }

                    if (!rule.enable)
                        continue;

                    var rulePreview = new RulePreview();
                    luaRules.Add(rulePreview);

                    var matchResult = rule.GetMatchResult();
                    for (var j = 0; j < matchResult.Count; ++j)
                    {
                        var matchData = matchResult[j];
                        var list = matchData.assetPaths;

                        for (var k = 0; k < list.Count; ++k)
                        {
                            var bundleInfo = RuleMatchAsset(list[k], matchData.bundleName, matchData.displayName, CompressType.LZMA, false);
                            rulePreview.AddBundleInfo(bundleInfo);
                        }
                    }
                }
            }
            //==========

            //========== top directory rules
            if (_config.topDirecotryRulesEnable)
            {
                var config_topDirectoryRules = _config.topDirectoryRules;

                for (int i = 0, max = config_topDirectoryRules.Count; i < max; i++)
                {
                    var rule = config_topDirectoryRules[i];

                    if (_config.displayProgressDialog)
                    {
                        var title = string.Format("收集DirectoryRules资源: {0}/{1}", i, max);
                        if (EditorUtility.DisplayCancelableProgressBar(title, "", i / (float)max))
                            break;
                    }

                    if (!rule.enable)
                        continue;

                    var rulePreview = new RulePreview();
                    topDirectoryRules.Add(rulePreview);

                    var matchResult = rule.GetMatchResult();
                    for (var j = 0; j < matchResult.Count; ++j)
                    {
                        var matchData = matchResult[j];
                        var list = matchData.assetPaths;

                        for (var k = 0; k < list.Count; ++k)
                        {
                            var bundleInfo = RuleMatchAsset(list[k], matchData.bundleName, matchData.displayName, rule.compressType, false);
                            rulePreview.AddBundleInfo(bundleInfo);
                        }
                    }
                }
            }
            //==========

            //========== explicit rules
            explicitRules.Clear();
            if (_config.explicitRulesEnable)
            {
                var config_explicitRules = _config.explicitRules;

                for (int i = 0, max = config_explicitRules.Count; i < max; i++)
                {
                    var rule = config_explicitRules[i];

                    if (_config.displayProgressDialog)
                    {
                        var title = string.Format("收集ExplicitRules资源: {0}/{1}", i, max);
                        if (EditorUtility.DisplayCancelableProgressBar(title, "", i / (float)max))
                            break;
                    }

                    if (!rule.enable)
                        continue;

                    if (string.IsNullOrEmpty(rule.bundleName))
                    {
                        Debug.LogError($"CollectAssets: ExplicitRule: Bundle Name为空: {i}");
                        continue;
                    }

                    var rulePreview = new RulePreview();
                    explicitRules.Add(rulePreview);

                    var matchResult = rule.GetMatchResult();
                    for (var j = 0; j < matchResult.Count; ++j)
                    {
                        var matchData = matchResult[j];
                        var list = matchData.assetPaths;

                        for (var k = 0; k < list.Count; ++k)
                        {
                            var bundleInfo = RuleMatchAsset(list[k], matchData.bundleName, matchData.displayName, rule.compressType, true);
                            rulePreview.AddBundleInfo(bundleInfo);
                        }
                    }
                }
            }
            //==========

            //========== filename rules
            fileNameRules.Clear();
            if (_config.fileNameRulesEnable)
            {
                var config_fileNameRules = _config.fileNameRules;

                for (int i = 0, max = config_fileNameRules.Count; i < max; i++)
                {
                    var rule = config_fileNameRules[i];

                    if (_config.displayProgressDialog)
                    {
                        var title = string.Format("收集FileNameRules资源: {0}/{1}", i, max);
                        if (EditorUtility.DisplayCancelableProgressBar(title, "", i / (float)max))
                            break;
                    }

                    if (!rule.enable)
                        continue;

                    var rulePreview = new RulePreview();
                    fileNameRules.Add(rulePreview);

                    var matchResult = rule.GetMatchResult();
                    for (var j = 0; j < matchResult.Count; ++j)
                    {
                        var matchData = matchResult[j];
                        var list = matchData.assetPaths;

                        for (var k = 0; k < list.Count; ++k)
                        {
                            var bundleInfo = RuleMatchAsset(list[k], matchData.bundleName, matchData.displayName, CompressType.LZMA, false);
                            rulePreview.AddBundleInfo(bundleInfo);
                        }
                    }
                }
            }
            //==========
        }


        /// <summary>
        /// 分析是否有资源出现在了多个AB包
        /// </summary>
        private void AnalysisAssets()
        {
            _tempStrList.Clear();

            int i = 0, max = _bundleInfoCache.Count;
            foreach (var entry in _bundleInfoCache)
            {
                var bundleInfo = entry.Value;

                if (_config.displayProgressDialog)
                {
                    var title = string.Format("分析依赖: {0}/{1}", i, max);
                    if (EditorUtility.DisplayCancelableProgressBar(title, bundleInfo.bundleName.name, i / (float)max))
                        break;
                }

                var bundleAssets = bundleInfo.assetInfoList;
                /*
                //场景文件不可以和非场景文件放一个包?
                if (bundleAssets.Exists(IsScene) //列表中存在场景文件
                    && !bundleAssets.TrueForAll(IsScene)) //不都是场景文件
                {
                    _conflicted.Add(bundleInfo.bundleName.name, bundleAssets.ToArray());
                }
                */

                for (var j = bundleAssets.Count - 1; j >= 0; --j)
                {
                    var bundleAsset = bundleAssets[j];

                    var assetPath = bundleAsset.assetPath.Replace('\\', '/');
                    var deps = AssetDatabase.GetDependencies(assetPath, true); //Bundle内的资源所依赖的资源, 包括自己。间接资源也要(解释看: DumpDifferentBetweenDeps)
                    //DumpDifferentBetweenDeps(assetPath);

                    if (deps.Length <= 0)
                        continue;

                    //Debug.Log($"===== dep: {assetPath}, ct: {deps.Length}");
                    var validCount = 0;
                    for (var k = 0; k < deps.Length; ++k)
                    {
                        var dep = deps[k];
                        if (dep == assetPath)
                            continue;

                        if (false == Utils.ValidateAsset(dep))
                            continue;

                        if (Directory.Exists(dep)) //文件夹
                            continue;

                        validCount++;
                        //Debug.Log($"{dep}");
                        if (_config.allShaderIn1Bundle && dep.EndsWith(".shader", StringComparison.OrdinalIgnoreCase))
                        {
                            _tempStrList.Add(dep);
                        }
                        else
                        {
                            var assetInfo = GetAssetInfo(dep);

                            if (null == assetInfo.ownBundle)
                            {
                                bundleInfo.AddDepAsset(assetInfo);

                                if (assetInfo.usedByBundles.Count > 1)
                                {
                                    //被多个包用到, 但没打Ab标签, 就会每个Ab包复制一份
                                    //Debug.Log($"redundant asset: {assetInfo.asset}");
                                    redundantAssets.Add(assetInfo);
                                }
                            }
                        }
                    }
                    //Debug.Log($"===== validCount: {validCount}");
                }

                i++;
            }

            //shader
            for (var j = 0; j < _tempStrList.Count; ++j)
            {
                ShaderFind(_tempStrList[j]);
            }
            _tempStrList.Clear();
        }


        void DumpDifferentBetweenDeps(string assetPath)
        {
            var inDirectDepSet = new HashSet<string>();
            var directDepSet = new HashSet<string>();

            //#CircleLoadingPanel依赖预制体Effect_Loading_01, 
            //#Effect_Loading_01依赖jiazaizhong_01.png, Effect_Begin_VS_Glow_01.mat
            //#Effect_Begin_VS_Glow_01.mat依赖Glow_27.png, Ui_Particle_Add.shader
            //
            //#CircleLoadingPanel对jiazaizhong_01.png是直接依赖, 对Glow_27.png, Ui_Particle_Add.shader这两个才是间接依赖
            //
            //#Glow_27.png没打ab标签, 这边如果不获取间接依赖那Glow_27.png就不会打入任何包

            var deps = AssetDatabase.GetDependencies(assetPath, true);
            foreach (var a in deps)
            {
                inDirectDepSet.Add(a);
            }

            var directDeps = AssetDatabase.GetDependencies(assetPath, false);
            foreach (var item in directDeps)
            {
                if (inDirectDepSet.Contains(item))
                {
                    inDirectDepSet.Remove(item);
                    continue;
                }

                Debug.Log($"间接依赖不存在的: {item}");
                directDepSet.Add(item);
            }
            foreach (var item in inDirectDepSet)
            {
                Debug.Log($"直接依赖不存在的: {item}");
            }
        }


        BundleInfo ShaderFind(string assetPath)
        {
            //所有shader放在单独的包, 不管被使用1次还是多次
            _tempSb.Append("shaders").Append(Utils.Bundle_Ext);
            var bundleName = _tempSb.BuildString();

            if (GetBundleInfo(bundleName, out var bundleInfo))
            {
                bundleInfo.bundleName.displayName = "shaders";
            }

            var assetInfo = GetAssetInfo(assetPath);
            bundleInfo.AddDirectIncludeAsset(assetInfo);

            _asset2BundleInfo[assetPath] = bundleInfo;

            return bundleInfo;
        }

        private void OptimizeAssets()
        {
            ////和场景文件放在一个包的那些资源, 都作为单独的包
            //int i = 0, max = _conflicted.Count;
            //foreach (var entry in _conflicted)
            //{
            //    /*
            //    var title = string.Format("优化冲突{0}/{1}", i, max);
            //    if (EditorUtility.DisplayCancelableProgressBar(title, entry.Key, i / (float)max))
            //        break;
            //        */

            //    var assetsArr = entry.Value;
            //    foreach (var assetPath in assetsArr)
            //        if (!IsScene(assetPath))
            //            _duplicated.Add(assetPath);
            //    i++;
            //}

            //for (i = 0, max = _duplicated.Count; i < max; i++)
            //{
            //    var assetInfo = _duplicated[i];
            //    var title = string.Format("优化冗余{0}/{1}", i, max);
            //    /*
            //    if (EditorUtility.DisplayCancelableProgressBar(title, asset, i / (float)max))
            //        break;
            //        */

            //    //OptimizeAsset(assetInfo);
            //}
        }

        /// <summary>
        /// 重复文件全部用单独的Ab包, shader全部放入一个包
        /// </summary>
        /// <param name="assetPath"></param>
        private void OptimizeAsset(string assetPath)
        {
            //var displayName = name.Replace('\\', '/');
            //var bundleName = Utility.GetMd5Hash(displayName) + Assets.Extension;

            //var bundleInfo = AddBundleAsset(bundleName, displayName, assetPath);
            //rule.AddBundleInfo(bundleInfo);
            if (assetPath.EndsWith(".shader"))
            {
                //所有shader放在一个单独的包
                var displayName = "shaders";
                var bundleName = displayName;// Utility.GetMd5Hash(displayName) + Assets.Extension;

                //AddBundleAsset(bundleName, displayName, assetPath);
            }
            //else
            //    _asset2BundleInfo[assetPath] = RuledBundleName(assetPath);
        }


        /// <summary>
        /// 序列化ruleAssets, ruleBundles等变量; 反序列话就用默认规则?
        /// </summary>
        private void Save()
        {
            this.allBundles.Clear();
            foreach (var entry in _bundleInfoCache)
            {
                //Debug.Log($"Save: bundleName: {entry.Key}, {entry.Value.bundleName.name}");
                entry.Value.Refresh();
                allBundles.Add(entry.Value);
            }

            //Array.Sort(ruleBundles, (a, b) =>
            //{
            //    return string.Compare(a.originalName, b.originalName);
            //});

            EditorUtility.ClearProgressBar();
            EditorUtility.SetDirty(this);
            AssetDatabase.SaveAssets();
        }


    } //end of class


}
